import bluetooth from '@ohos.bluetooth';
import Log from '../utils/Logger';
import { BleHelper } from './BleHelper';
import { BLEDataReceiver } from './BLEDataReceiver';
import ble from '@ohos.bluetooth.ble';
import List from '@ohos.util.List';
import { BusinessError } from '@ohos.base';
import constant from '@ohos.bluetooth.constant';
import util from '@ohos.util';
import wifiManager from '@ohos.wifiManager';

const TAG = "BleClient"
const PROTOCOL_HEADER = 0xAA55;
const DEF_MTU = 23; // AA55(2) + 长度(2)
const CUSTOM_UUID3 = '0000FFF1-0000-1000-8000-00805F9B34FB';
const SERVICE_UUID = '0000FFF0-0000-1000-8000-00805F9B34FB'

export interface BleCallBack {
  onData({deviceId,customData})
}

export interface BleStartFoundBack {
  onStartFound(isStart: boolean)
}

export interface DeviceData {
  code: number;
  ip: string;
  token: string;
}


export class BleClient {
  private static instance: BleClient;
  private bleCallBacks: List<BleCallBack> = new List<BleCallBack>();
  private bleStartFoundBack: List<BleStartFoundBack> = new List<BleStartFoundBack>();
  private mtu = DEF_MTU

  private gattClient: ble.GattClientDevice
  private call

  //
  // public token: string
  // public ip: string

  private isStartFound = false

  private constructor() {
  }

  getStartFound() {
    return this.isStartFound
  }

  public static getInstance(): BleClient {
    if (!BleClient.instance) {
      BleClient.instance = new BleClient()
    }
    return BleClient.instance
  }

  addBleCallBack(callBack: BleCallBack) {
    this.bleCallBacks.add(callBack)
  }

  removeBleCallBack(callBack: BleCallBack) {
    this.bleCallBacks.remove(callBack)
  }

  addBleStartFoundBack(callBack: BleStartFoundBack) {
    this.bleStartFoundBack.add(callBack)
  }

  removeBleStartFoundBack(callBack: BleStartFoundBack) {
    this.bleStartFoundBack.remove(callBack)
  }

  init() {
    bluetooth.on('stateChange', this.mWifiStateFunc);
    this.onBLEDeviceFind()
  }

  startFound() {
    Log.info(TAG, 'startFound')
    if (this.isStartFound) {
      Log.info(TAG, 'startFound is StartFound')
      return
    }
    this.setStartFound(true)

    let state = this.isBluetoothOpen(bluetooth.getState());
    if (state) {
      this.startBLEScan()
    } else {
      this.enableBluetooth()
    }
  }

  private setStartFound(start: boolean) {
    this.isStartFound = start
    if (this.bleStartFoundBack) {
      this.bleStartFoundBack.forEach((call) => {
        call.onStartFound(this.isStartFound)
      })
    }
  }

  stopBLEScan() {
    Log.info(TAG, 'stopBLEScan')
    if (!this.isStartFound) {
      Log.info(TAG, 'stopBLEScan is stopBLEScan')
      return
    }
    this.setStartFound(false)
    try {
      Log.info(TAG, 'stopBLEScan start')
      ble.stopBLEScan()
      Log.info(TAG, 'stopBLEScan success')
      return true
    } catch (err) {
      Log.error(TAG, 'stopBLEScan fail', JSON.stringify(err) ?? '')
      return false
    }
  }

  private onBLEDeviceFind(): boolean {
    try {
      ble.on('BLEDeviceFind', (data: Array<ble.ScanResult>) => {
        Log.info(TAG, 'bluetooth device find = ' + JSON.stringify(data));
        for (let i = 0; i < data.length; i++) {
          const result = this.parseBleAdvertisement(data[i])
          try {
            const customData: string = result.manufacturerData.customData
            if (customData.startsWith("EGH200")) {
              Log.info(TAG, 'onBLEDeviceFind find = ' + customData);
              if (this.bleCallBacks) {
                this.bleCallBacks.forEach((call) => {
                  call.onData({ deviceId: data[i].deviceId, customData })
                })
              }
            }
          } catch (err) {
          }

        }
      })
      Log.info(TAG, 'onBLEDeviceFind success')
      return true
    } catch (err) {
      Log.error(TAG, 'onBLEDeviceFind fail', JSON.stringify(err) ?? '')
      return false
    }
  }

  private parseBleAdvertisement(bleAdvertisement) {
    const data = bleAdvertisement.data
    const parsed = this.parse2(data);
    Log.info(TAG, 'parseBleAdvertisement ' + JSON.stringify(parsed))
    return parsed
  }

  private parse2(data) {
    const result = {
      manufacturerData: null,
      serviceUuids: [],
      flags: null,
      txPowerLevel: null
    };

    let index = 0;
    const bytes = Object.values(data).map(Number);

    while (index < bytes.length) {
      const length = bytes[index++];
      if (length === 0) break;

      const type = bytes[index++];
      const valueBytes = bytes.slice(index, index + length - 1);
      index += length - 1;

      switch (type) {
        case 0xFF: // Manufacturer Specific Data
          result.manufacturerData = {
            companyId: (valueBytes[1] << 8) | valueBytes[0],
            customData: String.fromCharCode(...valueBytes.slice(2))
          };
          break;

        case 0x03: // Complete List of 16-bit Service UUIDs
        case 0x02: // Incomplete List of 16-bit Service UUIDs
          for (let i = 0; i < valueBytes.length; i += 2) {
            const uuid = (valueBytes[i+1] << 8) | valueBytes[i];
            result.serviceUuids.push(
              `0000${uuid.toString(16).padStart(4, '0')}-0000-1000-8000-00805F9B34FB`
            );
          }
          break;

        case 0x01: // Flags
          result.flags = valueBytes[0];
          break;

        case 0x0A: // Tx Power Level
          result.txPowerLevel = valueBytes[0];
          break;
      }
    }

    return result;
  }

  private mWifiStateFunc = state => {
    Log.info(TAG, `onStateChange callback, state: ${JSON.stringify(state)}`);
    let isOpen = this.isBluetoothOpen(state);
    if (state == bluetooth.BluetoothState.STATE_ON) {
      this.startBLEScan()
    }
  }

  private startBLEScan() {
    Log.info(TAG, `startBLEScan...`);

    BleHelper.startBLEScan(null, {
      interval: 500,
      dutyMode: ble.ScanDuty.SCAN_MODE_LOW_LATENCY,
      matchMode: ble.MatchMode.MATCH_MODE_AGGRESSIVE
    })
  }


  private enableBluetooth(): boolean {
    let result = bluetooth.enableBluetooth();
    Log.info(TAG, `enableBluetooth, result: ${result}`);
    return result;
  }

  private isBluetoothOpen(state: number): boolean {
    return state == bluetooth.BluetoothState.STATE_ON || state == bluetooth.BluetoothState.STATE_BLE_ON
      || state == bluetooth.BluetoothState.STATE_TURNING_ON || state == bluetooth.BluetoothState.STATE_BLE_TURNING_ON;
  }

  // goConnect(deviceId: string) {
  //   this.stopBLEScan()
  //   this.connect(deviceId)
  // }

  getDevice(deviceId: string, call) {
    this.stopBLEScan()
    this.call = call
    this.connect(deviceId)
  }

  // 连接设备
  private connect(deviceId: string) {
    try {
      if (this.gattClient) {
        Log.error(TAG, "已经有一个连接了。。。")
        this.disconnect()
      }
      this.gattClient = ble.createGattClientDevice(deviceId);
      const gattClient = this.gattClient
      gattClient.connect();
      Log.info(TAG, `Connected to ${deviceId}`);

      gattClient.on('BLEMtuChange', (mtu: number) => {
        Log.info(TAG, 'BLEMtuChange:' + JSON.stringify(mtu));
      });

      gattClient.on('BLEConnectionStateChange', (state: ble.BLEConnectionChangeState) => {
        Log.info(TAG, 'BLEConnectionStateChange:' + JSON.stringify(state));
        if (deviceId === state.deviceId && state.state === constant.ProfileConnectionState.STATE_CONNECTED) {
          this.discoverServices(gattClient)
        }
      });
      return true
    } catch (err) {
      Log.error(TAG, 'connect error:' + JSON.stringify(err));
      return false
    }
  }

  //22~512
  private setBLEMtuSize(device: ble.GattClientDevice, mtu: number) {
    Log.info(TAG, 'setBLEMtuSize ' + mtu);
    try {
      device.setBLEMtuSize(mtu);
      this.mtu = mtu
    } catch (err) {
      Log.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
    }
  }

  // 发现服务
  private discoverServices(gattClient: ble.GattClientDevice) {
    gattClient.getServices((err: BusinessError, gattServices: Array<ble.GattService>) => {
      Log.info(TAG, `getServices :${JSON.stringify(err)}`);
      if (!err) {
        let services: Array<ble.GattService> = gattServices;
        this.mtu = DEF_MTU
        Log.info(TAG, 'BLE services size is ' + services.length);
        for (let i = 0; i < services.length; i++) {
          Log.info(TAG, 'BLE serviceUuid is ' + services[i].serviceUuid);
          if (services[i].serviceUuid === SERVICE_UUID) {
            this.setBLEMtuSize(gattClient, 512) //
            this.senPin(gattClient, services[i])
          }
        }
      }
    });
  }

  // 发送pin码
  private async senPin(gattClient: ble.GattClientDevice, service: ble.GattService): Promise<void> {
    Log.info(TAG, `senPin service:${JSON.stringify(service)}`);
    try {
      const characteristics = service.characteristics;
      const targetChar = characteristics.find(c => c.characteristicUuid === CUSTOM_UUID3);

      if (!targetChar) {
        Log.error(TAG, 'Target characteristic not found');
      }

      const dataReceiver = new BLEDataReceiver();

      // 回调
      gattClient.on('BLECharacteristicChange', async (characteristicChangeReq: ble.BLECharacteristic) => {
        Log.info(TAG, 'BLECharacteristicChange:' + JSON.stringify(characteristicChangeReq));
        Log.info(TAG, 'BLECharacteristicChange printArrayBuffer:' + this.printArrayBuffer(characteristicChangeReq.characteristicValue));
        const result = dataReceiver.handleChunk(characteristicChangeReq.characteristicValue);
        if (result) {
          try {
            const jsonData = JSON.parse(result);
            Log.info(TAG, '收到完整数据:' + JSON.stringify(jsonData));
            if (jsonData.cmd && jsonData.cmd === "pin") {
              if (jsonData.data && jsonData.data.status === 0) {
                // this.token = jsonData.data.token
                //this.ip = jsonData.data.ip
                const config: wifiManager.WifiDeviceConfig = {
                  ssid: jsonData.data.ssid,
                  preSharedKey: jsonData.data.password || '',
                  securityType: jsonData.data.password ? wifiManager.WifiSecurityType.WIFI_SEC_TYPE_PSK : wifiManager.WifiSecurityType.WIFI_SEC_TYPE_OPEN
                }
               await this.connectToDevice(config)
                if (this.call) {
                  const mockDevice: DeviceData = {
                    code: 0,
                    ip: jsonData.data.ip,
                    token: jsonData.data.token
                  };
                  this.call(mockDevice)
                }
                this.disconnect()
              } else if (jsonData.data && jsonData.data.status === -1) {
                if (this.call) {
                  const mockDevice: DeviceData = {
                    code: -1,
                    ip: "",
                    token: ""
                  };
                  this.call(mockDevice)
                }
                this.disconnect()
              }
            }
          } catch (e) {
            Log.error(TAG, 'JSON解析错误:', e);
          }
        }
      });

      await this.enableNotifications(gattClient, targetChar)

      const message = { "cmd": "pin", "data": { "pinCode": "1234" } };
      // this.writeCharacteristic(gattClient, targetChar, "1234")
      await this.sendLargeObjectData(gattClient, targetChar, message)

    } catch (err) {
      Log.error(TAG, 'enableNotifications error:', (err as BusinessError).message);
      throw err;
    }
  }

  async sendLargeObjectData(gattClient: ble.GattClientDevice, characteristic: ble.BLECharacteristic, jsonData: object) {
    const jsonStr = JSON.stringify(jsonData);
    await this.sendLargeData(gattClient, characteristic, jsonStr)
  }

  private async connectToDevice2(config: wifiManager.WifiDeviceConfig): Promise<boolean> {
    try {
      Log.info(TAG, 'connectToDevice ' + JSON.stringify(config))
      await this.enableWifi()
      wifiManager.connectToDevice(config);
      Log.info(TAG, 'connectToDevice success')
      return true
    } catch (err) {
      Log.error(TAG, 'connectToDevice fail', JSON.stringify(err) ?? '')
      return false
    }
  }

  private async connectToDevice(config: wifiManager.WifiDeviceConfig): Promise<boolean> {
    try {
      Log.info(TAG, 'connectToDevice ' + JSON.stringify(config))
      let  result =  await this.enableWifi()
      const isConnected = this.isConnected()
      if (isConnected) {
        const linkedInfo = await this.getLinkedInfo()
        if (linkedInfo.ssid === config.ssid) {
          return true
        }
        result = this.wifiDisconnect()
        Log.info(TAG, 'connect disconnect', result)
        if (result) {
          result = await this.waitConnect(false)
          Log.info(TAG, 'connect disconnect result', result)
        }
      }
      if (result) {
        result = await this.connectToDevice2(config)
        Log.info(TAG, 'connect connectToDevice', result)
      }
      if (result) {
        result = await this.waitConnect(true)
        Log.info(TAG, 'connect result', result)
      }
      if (result) {
        this.removeOtherDeviceConfig(config.ssid)
      }

      return true
    } catch (err) {
      Log.error(TAG, 'connectToDevice fail', JSON.stringify(err) ?? '')
      return false
    }
  }

  private removeOtherDeviceConfig(ssid: string) {
    const configs = this.getDeviceConfigs();
    for (let i = 0; i < configs.length; i++) {
      if (configs[i].ssid !== ssid) {
        this.removeDevice(configs[i].netId)
      }
    }
  }

  private getDeviceConfigs(): wifiManager.WifiDeviceConfig[] {
    try {
      const deviceConfigs = wifiManager.getDeviceConfigs();
      Log.info(TAG, 'getDeviceConfigs success', JSON.stringify(deviceConfigs.length))
      return deviceConfigs
    } catch (err) {
      Log.error(TAG, 'getDeviceConfigs fail', JSON.stringify(err) ?? '')
      return []
    }
  }

  private removeDevice(id: number): boolean {
    try {
      wifiManager.removeDevice(id);
      Log.info(TAG, 'removeDevice success', JSON.stringify(id))
      return true
    } catch (err) {
      Log.error(TAG, 'removeDevice fail', JSON.stringify(err) ?? '')
      return false
    }
  }

  private async waitConnect(state: boolean): Promise<boolean> {
    const  maxWaitConnectNum = 30
    let currentState = this.isConnected()
    let num = 0
    while (num < maxWaitConnectNum && currentState !== state) {
      await this.waitTime()
      currentState =  this.isConnected()
      num = num + 1
    }
    return currentState === state
  }

  private wifiDisconnect(): boolean {
    try {
      wifiManager.disconnect();
      Log.info(TAG, 'disconnect success')
      return true
    } catch (err) {
      Log.error(TAG, 'disconnect fail', JSON.stringify(err) ?? '')
      return false
    }
  }

  private async getLinkedInfo(): Promise<wifiManager.WifiLinkedInfo> {
    try {
      const linkedInfo = await wifiManager.getLinkedInfo();
      Log.info(TAG, 'getLinkedInfo success', JSON.stringify(linkedInfo))
      return linkedInfo
    } catch (err) {
      Log.error(TAG, 'getLinkedInfo fail', JSON.stringify(err) ?? '')
      return null
    }
  }

  private isConnected(): boolean {
    try {
      const result = wifiManager.isConnected();
      Log.info(TAG, 'isConnected success', result)
      return result
    } catch (err) {
      Log.error(TAG, 'isConnected fail', JSON.stringify(err) ?? '')
      return false
    }
  }

  private async enableWifi(): Promise<boolean> {
    const isWifiActive = wifiManager.isWifiActive();
    let result = false
    if (isWifiActive) {
      result = true
    } else {
      result = this.enableWifiSys()
      if (result) {
        result = await this.waitState(true)
      }
    }
    return result
  }


  private async waitState(state: boolean): Promise<boolean> {
    const maxWaitStateNum = 10
    let currentState = wifiManager.isWifiActive();
    let num = 0
    while (num <  maxWaitStateNum && currentState !== state) {
      await this.waitTime()
      currentState = wifiManager.isWifiActive()
      num = num + 1
    }
    return currentState === state
  }

  private async waitTime(time: number = 1000) {
    return new Promise<void>(resolve => {
      setTimeout(() => {
        resolve()
      }, time)
    })
  }

  private enableWifiSys(): boolean {
    try {
      wifiManager.enableWifi();
      Log.info(TAG, 'enableWifi success')
      return true
    } catch (err) {
      Log.error(TAG, 'enableWifi fail', JSON.stringify(err) ?? '')
      return false
    }
  }

  // 分片发送函数
  async sendLargeData(gattClient: ble.GattClientDevice, characteristic: ble.BLECharacteristic, json: string) {
    // 1. 准备数据
    const jsonBuffer = this.stringToBytes(json);
    const mtu = this.mtu; // 默认MTU
    const maxPayload = mtu - 3; // 实际可用20字节

    // 2. 创建包含协议头和数据的完整缓冲区
    const header = new DataView(new ArrayBuffer(4));
    header.setUint16(0, PROTOCOL_HEADER, false); // AA55 (大端)
    header.setUint16(2, jsonBuffer.byteLength, false); // 数据长度 (大端)

    // 合并头部和JSON数据
    const fullData = new Uint8Array(4 + jsonBuffer.byteLength);
    fullData.set(new Uint8Array(header.buffer), 0); // 添加头部
    fullData.set(new Uint8Array(jsonBuffer), 4); // 添加JSON数据

    // 3. 分片发送
    const chunkCount = Math.ceil(fullData.length / maxPayload);

    for (let i = 0; i < chunkCount; i++) {
      const start = i * maxPayload;
      const end = Math.min(start + maxPayload, fullData.length);
      const chunk = fullData.slice(start, end);

      await this.writeCharacteristic(gattClient, characteristic, chunk.buffer);
    }
  }

  private async enableNotifications(gattClient: ble.GattClientDevice, characteristic: ble.BLECharacteristic): Promise<void> {
    try {
      await gattClient.setCharacteristicChangeNotification(characteristic, true);
      Log.info(TAG, 'setCharacteristicChangeNotification true');




    } catch (err) {
      Log.error(TAG, 'writeCharacteristic error:', (err as BusinessError).message);
    }

  }


  // 写入特征值 client端向低功耗蓝牙设备写入特定的特征值
  async writeCharacteristic(gattClient: ble.GattClientDevice, characteristic: ble.BLECharacteristic, arr: ArrayBuffer) {
    try {
      characteristic.characteristicValue = arr
      await gattClient.writeCharacteristicValue(characteristic, ble.GattWriteType.WRITE);
      Log.info(TAG, 'Value written ' + this.printArrayBuffer(arr));
    } catch (err) {
      Log.error(TAG, 'writeCharacteristic error:', (err as BusinessError).message);
    }
  }

  private disconnect() {
    try {
      if (this.gattClient) {
        this.gattClient.off('BLEMtuChange', (mtu: number) => {
          Log.info(TAG, 'off BLEMtuChange:' + JSON.stringify(mtu));
        });

        this.gattClient.off('BLEConnectionStateChange', (state: ble.BLEConnectionChangeState) => {
          Log.info(TAG, 'off BLEConnectionStateChange:' + JSON.stringify(state));
        });
        this.gattClient.disconnect();
      }
      this.gattClient = null
      this.call = null
      Log.info(TAG, `disconnect`);
      return true
    } catch (err) {
      Log.error(TAG, 'disconnect error:' + JSON.stringify(err));
      return false
    }
  }

  private stringToBytes(str: string): ArrayBuffer {
    const encoder = new util.TextEncoder();
    return encoder.encode(str).buffer;
  }

  private printArrayBuffer(buffer: ArrayBuffer): string {
    const uint8Array = new Uint8Array(buffer);
    // 打印十六进制
    return Array.from(uint8Array)
      .map(b => b.toString(16).padStart(2, '0'))
      .join(' ')
  }
}